1 /**
2    This module is an attempt to alleviate compile times by including the bare
3    minimum. The idea is that while the reporting usually done by unit-threaded
4    is welcome, it only really matters when tests fail. Otherwise, no news is
5    good news.
6 
7    Likewise, naming and selecting tests are features used when certain tests
8    fail. The usual way to run tests is to run all of them and be happy if
9    they all pass.
10 
11    This module makes it so that unit-threaded gets out of the way, and if
12    needed the full features can be turned on at the cost of compiling
13    much more slowly.
14 
15    There aren't even any template constraints on the `should` functions
16    to avoid imports as much as possible.
17  */
18 module unit_threaded.light;
19 
20 alias UnitTestException = Exception;
21 
22 /**
23    Dummy version of runTests so "normal" code compiles.
24  */
25 int runTests(T...)(in string[] args) {
26     return runTestsImpl;
27 }
28 
29 /// ditto
30 int runTests(T)(string[] args, T testData) {
31     return runTestsImpl;
32 }
33 
34 int runTestsImpl() {
35     import core.runtime : Runtime;
36     import core.stdc.stdio : printf;
37 
38     try {
39 
40         Runtime.moduleUnitTester();
41 
42         printf("\n");
43         version (Posix)
44             printf("\033[32;1mOk\033[0;;m");
45         else
46             printf("Ok");
47 
48         printf(": All tests passed\n\n");
49 
50         return 0;
51     } catch (Throwable _)
52         return 1;
53 }
54 
55 /**
56    Dummy version so "normal" code compiles
57  */
58 int[] allTestData(T...)() {
59     return [];
60 }
61 
62 /**
63    No-op version of writelnUt
64  */
65 void writelnUt(T...)(auto ref T args) {
66 
67 }
68 
69 /**
70    Same as unit_threaded.property.check
71  */
72 void check(alias F)(int numFuncCalls = 100, in string file = __FILE__, in size_t line = __LINE__) @trusted {
73     import unit_threaded.property : utCheck = check;
74 
75     utCheck!F(numFuncCalls, file, line);
76 }
77 
78 /**
79    Same as unit_threaded.property.checkCustom
80  */
81 void checkCustom(alias Generator, alias Predicate)(int numFuncCalls = 100,
82         in string file = __FILE__, in size_t line = __LINE__) @trusted {
83     import unit_threaded.property : utCheckCustom = checkCustom;
84 
85     utCheckCustom!(Generator, Predicate)(numFuncCalls, file, line);
86 }
87 
88 /**
89    Generic output interface
90  */
91 interface Output {
92     void send(in string output) @safe;
93     void flush() @safe;
94 }
95 
96 /**
97    Dummy version of unit_threaded.testcase.TestCase
98  */
99 class TestCase {
100     abstract void test();
101     void setup() {
102     }
103 
104     void shutdown() {
105     }
106 
107     static TestCase currentTest() {
108         return new class TestCase {
109             override void test() {
110             }
111         };
112     }
113 
114     Output getWriter() {
115         return new class Output {
116             override void send(in string output) {
117             }
118 
119             override void flush() {
120             }
121         };
122     }
123 }
124 
125 /**
126    Same as unit_threaded.mock.mock
127  */
128 auto mock(T)() {
129     import unit_threaded.mock : utMock = mock;
130 
131     return utMock!T;
132 }
133 
134 /**
135    Same as unit_threaded.mock.mockStruct
136  */
137 auto mockStruct(T...)(auto ref T returns) {
138     import unit_threaded.mock : utMockStruct = mockStruct;
139 
140     return utMockStruct(returns);
141 }
142 
143 /**
144    Throw if condition is not true.
145  */
146 void shouldBeTrue(E)(lazy E condition, in string file = __FILE__, in size_t line = __LINE__) {
147     assert_(cast(bool) condition(), file, line);
148 }
149 
150 /// Throw if condition not false.
151 void shouldBeFalse(E)(lazy E condition, in string file = __FILE__, in size_t line = __LINE__) {
152     assert_(!cast(bool) condition(), file, line);
153 }
154 
155 /// Assert value is equal to expected
156 void shouldEqual(V, E)(auto ref V value, auto ref E expected, in string file = __FILE__,
157         in size_t line = __LINE__) {
158 
159     void checkInputRange(T)(auto ref const(T) _) @trusted {
160         auto obj = cast(T) _;
161         bool e = obj.empty;
162         auto f = obj.front;
163         obj.popFront;
164     }
165 
166     enum isInputRange(T) = is(T : Elt[], Elt) || is(typeof(checkInputRange(T.init)));
167 
168     static if (is(V == class)) {
169         assert_(value.tupleof == expected.tupleof, file, line);
170     } else static if (isInputRange!V && isInputRange!E) {
171         auto ref unqual(T)(auto ref const(T) obj) @trusted {
172             static if (is(T == void[]))
173                 return cast(ubyte[]) obj;
174             else
175                 return cast(T) obj;
176         }
177 
178         import std.algorithm : equal;
179 
180         assert_(equal(unqual(value), unqual(expected)), file, line);
181     } else {
182         assert_(cast(const) value == cast(const) expected, file, line);
183     }
184 }
185 
186 /// Assert value is not equal to expected.
187 void shouldNotEqual(V, E)(in auto ref V value, in auto ref E expected,
188         in string file = __FILE__, in size_t line = __LINE__) {
189     assert_(value != expected, file, line);
190 }
191 
192 /// Assert value is null.
193 void shouldBeNull(T)(in auto ref T value, in string file = __FILE__, in size_t line = __LINE__) {
194     assert_(value is null, file, line);
195 }
196 
197 /// Assert value is not null
198 void shouldNotBeNull(T)(in auto ref T value, in string file = __FILE__, in size_t line = __LINE__) {
199     assert_(value !is null, file, line);
200 }
201 
202 enum isLikeAssociativeArray(T, K) = is(typeof({
203             if (K.init in T) {
204             }
205             if (K.init !in T) {
206             }
207         }));
208 static assert(isLikeAssociativeArray!(string[string], string));
209 static assert(!isLikeAssociativeArray!(string[string], int));
210 
211 /// Assert that value is in container.
212 void shouldBeIn(T, U)(in auto ref T value, in auto ref U container,
213         in string file = __FILE__, in size_t line = __LINE__)
214         if (isLikeAssociativeArray!(U, T)) {
215     assert_(cast(bool)(value in container), file, line);
216 }
217 
218 /// ditto.
219 void shouldBeIn(T, U)(in auto ref T value, U container, in string file = __FILE__,
220         in size_t line = __LINE__) if (!isLikeAssociativeArray!(U, T)) {
221     import std.algorithm : find;
222     import std.array : empty;
223 
224     assert_(!find(container, value).empty, file, line);
225 }
226 
227 /// Assert value is not in container.
228 void shouldNotBeIn(T, U)(in auto ref T value, in auto ref U container,
229         in string file = __FILE__, in size_t line = __LINE__)
230         if (isLikeAssociativeArray!U) {
231     assert_(!cast(bool)(value in container), file, line);
232 }
233 
234 /// ditto.
235 void shouldNotBeIn(T, U)(in auto ref T value, U container, in string file = __FILE__,
236         in size_t line = __LINE__) if (!isLikeAssociativeArray!(U, T)) {
237     import std.algorithm : find;
238     import std.array : empty;
239 
240     assert_(find(container, value).empty, file, line);
241 }
242 
243 /// Assert that expr throws.
244 void shouldThrow(T : Throwable = Exception, E)(lazy E expr,
245         in string file = __FILE__, in size_t line = __LINE__) {
246     auto threw = false;
247     () @trusted{
248         try {
249             expr();
250         } catch (T _) {
251             threw = true;
252         }
253     }();
254     assert_(threw, file, line);
255 }
256 
257 /// Assert that expr throws an Exception that must have the type E, derived types won't do.
258 void shouldThrowExactly(T : Throwable = Exception, E)(lazy E expr,
259         in string file = __FILE__, in size_t line = __LINE__) {
260     T throwable = null;
261 
262     () @trusted{
263         try {
264             expr();
265             assert_(false, file, line);
266         } catch (T t) {
267             throwable = t;
268         }
269     }();
270 
271     //Object.opEquals is @system and impure
272     const sameType = () @trusted{
273         return throwable !is null && typeid(throwable) == typeid(T);
274     }();
275     assert_(sameType, file, line);
276 
277 }
278 
279 /// Assert that expr doesn't throw
280 void shouldNotThrow(T : Throwable = Exception, E)(lazy E expr,
281         in string file = __FILE__, in size_t line = __LINE__) {
282     () @trusted{
283         try
284             expr();
285         catch (T _)
286             assert_(false, file, line);
287     }();
288 }
289 
290 /// Assert that expr throws and the exception message is msg.
291 void shouldThrowWithMessage(T : Throwable = Exception, E)(lazy E expr, string msg,
292         string file = __FILE__, size_t line = __LINE__) {
293     T throwable = null;
294 
295     () @trusted{
296         try {
297             expr();
298         } catch (T ex) {
299             throwable = ex;
300         }
301     }();
302 
303     assert_(throwable !is null && throwable.msg == msg, file, line);
304 }
305 
306 /// Assert that value is approximately equal to expected.
307 void shouldApproxEqual(V, E)(in V value, in E expected, string file = __FILE__,
308         size_t line = __LINE__) {
309     import std.math : approxEqual;
310 
311     assert_(approxEqual(value, expected), file, line);
312 }
313 
314 /// assert that rng is empty.
315 void shouldBeEmpty(R)(in auto ref R rng, in string file = __FILE__, in size_t line = __LINE__) {
316     import std.range : isInputRange;
317     import std.traits : isAssociativeArray;
318     import std.array;
319 
320     static if (isInputRange!R)
321         assert_(rng.empty, file, line);
322     else static if (isAssociativeArray!R)
323         () @trusted{ assert_(rng.keys.empty, file, line); }();
324     else
325         static assert(false, "Cannot call shouldBeEmpty on " ~ R.stringof);
326 }
327 
328 /// Assert that rng is not empty.
329 void shouldNotBeEmpty(R)(in auto ref R rng, in string file = __FILE__, in size_t line = __LINE__) {
330     import std.range : isInputRange;
331     import std.traits : isAssociativeArray;
332     import std.array;
333 
334     static if (isInputRange!R)
335         assert_(!rnd.empty, file, line);
336     else static if (isAssociativeArray!R)
337         () @trusted{ assert_(!rng.keys.empty, file, line); }();
338     else
339         static assert(false, "Cannot call shouldBeEmpty on " ~ R.stringof);
340 }
341 
342 /// Assert that t should be greater than u.
343 void shouldBeGreaterThan(T, U)(in auto ref T t, in auto ref U u,
344         in string file = __FILE__, in size_t line = __LINE__) {
345     assert_(t > u, file, line);
346 }
347 
348 /// Assert that t should be smaller than u.
349 void shouldBeSmallerThan(T, U)(in auto ref T t, in auto ref U u,
350         in string file = __FILE__, in size_t line = __LINE__) {
351     assert_(t < u, file, line);
352 }
353 
354 /// Assert that value is the same set as expected (i.e. order doesn't matter)
355 void shouldBeSameSetAs(V, E)(in auto ref V value, in auto ref E expected,
356         in string file = __FILE__, in size_t line = __LINE__) {
357     assert_(isSameSet(value, expected), file, line);
358 }
359 
360 /// Assert that value is not the same set as expected.
361 void shouldNotBeSameSetAs(V, E)(in auto ref V value, in auto ref E expected,
362         in string file = __FILE__, in size_t line = __LINE__) {
363     assert_(!isSameSet(value, expected), file, line);
364 }
365 
366 private bool isSameSet(T, U)(in auto ref T t, in auto ref U u) {
367     import std.array : array;
368     import std.algorithm : canFind;
369 
370     //sort makes the element types have to implement opCmp
371     //instead, try one by one
372     auto ta = t.array;
373     auto ua = u.array;
374     if (ta.length != ua.length)
375         return false;
376     foreach (element; ta) {
377         if (!ua.canFind(element))
378             return false;
379     }
380 
381     return true;
382 }
383 
384 /// Assert that actual and expected represent the same JSON (i.e. formatting doesn't matter)
385 void shouldBeSameJsonAs(in string actual, in string expected,
386         in string file = __FILE__, in size_t line = __LINE__) @trusted // not @safe pure due to parseJSON
387         {
388     import std.json : parseJSON, JSONException;
389 
390     auto parse(in string str) {
391         try
392             return str.parseJSON;
393         catch (JSONException ex) {
394             assert_(false, "Failed to parse " ~ str, file, line);
395         }
396         assert(0);
397     }
398 
399     assert_(parse(actual) == parse(expected), file, line);
400 }
401 
402 private void assert_(in bool value, in string file, in size_t line) @safe pure {
403     assert_(value, "Assertion failure", file, line);
404 }
405 
406 private void assert_(bool value, in string message, in string file, in size_t line) @trusted pure {
407     if (!value)
408         throw new Exception(message, file, line);
409 }
410 
411 void fail(in string output, in string file, in size_t line) @safe pure {
412     assert_(false, output, file, line);
413 }